import datetime
import os
import random
import string
import uuid
import pytest
import requests
import boto3
from fixtures import get_order, get_product # pylint: disable=import-error,no-name-in-module
from helpers import get_parameter # pylint: disable=import-error,no-name-in-module


@pytest.fixture(scope="module")
def warehouse_table_name():
    """
    Warehouse DynamoDB table name
    """

    return get_parameter("/ecommerce/{Environment}/warehouse/table/name")


@pytest.fixture(scope="module")
def delivery_table_name():
    """
    Delivery DynamoDB table name
    """

    return get_parameter("/ecommerce/{Environment}/delivery/table/name")


@pytest.fixture(scope="module")
def user_pool_id():
    """
    Cognito User Pool ID
    """

    return get_parameter("/ecommerce/{Environment}/users/user-pool/id")


@pytest.fixture
def api_id():
    """
    Frontend GraphQL API ID
    """

    return get_parameter("/ecommerce/{Environment}/frontend-api/api/id")


@pytest.fixture
def api_url():
    """
    Frontend GraphQL API URL
    """

    return get_parameter("/ecommerce/{Environment}/frontend-api/api/url")


@pytest.fixture(scope="module")
def password():
    """
    Generate a unique password for the user
    """

    return "".join(
        random.choices(string.ascii_uppercase, k=10) +
        random.choices(string.ascii_lowercase, k=10) +
        random.choices(string.digits, k=5) +
        random.choices(string.punctuation, k=3)
    )


@pytest.fixture(scope="module")
def email():
    """
    Generate a unique email address for the user
    """

    return "".join(random.choices(string.ascii_lowercase, k=20))+"@example.local"


@pytest.fixture(scope="module")
def client_id(user_pool_id):
    """
    Return a user pool client
    """

    cognito = boto3.client("cognito-idp")

    # Create a Cognito User Pool Client
    response = cognito.create_user_pool_client(
        UserPoolId=user_pool_id,
        ClientName="ecommerce-{}-frontend-api-test".format(os.environ["ECOM_ENVIRONMENT"]),
        GenerateSecret=False,
        ExplicitAuthFlows=["ADMIN_NO_SRP_AUTH"]
    )

    # Return the client ID
    client_id = response["UserPoolClient"]["ClientId"]
    yield client_id

    # Delete the client
    cognito.delete_user_pool_client(
        UserPoolId=user_pool_id,
        ClientId=client_id
    )


@pytest.fixture(scope="module")
def user_id(user_pool_id, email, password):
    """
    User ID generated by Cognito
    """

    cognito = boto3.client("cognito-idp")

    # Create a Cognito user
    response = cognito.admin_create_user(
        UserPoolId=user_pool_id,
        Username=email,
        UserAttributes=[{
            "Name": "email",
            "Value": email
        }],
        MessageAction="SUPPRESS"
    )
    user_id = response["User"]["Username"]
    cognito.admin_set_user_password(
        UserPoolId=user_pool_id,
        Username=user_id,
        Password=password,
        Permanent=True
    )

    cognito.admin_add_user_to_group(
        UserPoolId=user_pool_id,
        Username=user_id,
        GroupName="admin"
    )

    # Return the user ID
    yield user_id

    # Delete the user
    cognito.admin_delete_user(
        UserPoolId=user_pool_id,
        Username=user_id
    )


@pytest.fixture(scope="module")
def jwt_token(user_pool_id, user_id, client_id, email, password):
    """
    Returns a JWT token for API Gateway
    """

    cognito = boto3.client("cognito-idp")

    response = cognito.admin_initiate_auth(
        UserPoolId=user_pool_id,
        ClientId=client_id,
        AuthFlow="ADMIN_NO_SRP_AUTH",
        AuthParameters={
            "USERNAME": email,
            "PASSWORD": password
        }
    )

    return response["AuthenticationResult"]["IdToken"]


def test_get_new_packaging_request_ids(jwt_token, api_url, warehouse_table_name):
    """
    Test getNewPackagingRequestIds
    """

    order_metadata = {
        "orderId": str(uuid.uuid4()),
        "productId": "__metadata",
        "modifiedDate": datetime.datetime.now().isoformat(),
        "newDate": datetime.datetime.now().isoformat(),
        "status": "NEW"
    }

    print(order_metadata)

    # Seed the database
    table = boto3.resource("dynamodb").Table(warehouse_table_name) # pylint: disable=no-member
    table.put_item(Item=order_metadata)

    # Make requests
    headers = {"Authorization": jwt_token}
    def get_ids(next_token=None):
        if next_token:
            req_data = {
                "query": """
                query ($nextToken: String!) {
                    getNewPackagingRequestIds(nextToken: $nextToken) {
                        nextToken
                        packagingRequestIds
                    }
                }
                """,
                "variables": {
                    "nextToken": next_token
                }
            }
        else:
            req_data = {
                "query": """
                query {
                    getNewPackagingRequestIds {
                        nextToken
                        packagingRequestIds
                    }
                }
                """
            }

        response = requests.post(api_url, json=req_data, headers=headers)
        data = response.json()

        print(jwt_token)
        print(data)

        assert "data" in data
        assert data["data"] is not None
        assert "getNewPackagingRequestIds" in data["data"]
        return data["data"]["getNewPackagingRequestIds"]

    found = False
    ids = get_ids()
    if order_metadata["orderId"] in ids["packagingRequestIds"]:
        found = True
    while found == False and ids.get("nextToken", None) is not None:
        ids = get_ids(ids["nextToken"])
        if order_metadata["orderId"] in ids["packagingRequestIds"]:
            found = True

    assert found == True

    # Clean database
    table.delete_item(Key={
        "orderId": order_metadata["orderId"],
        "productId": order_metadata["productId"]
    })


def test_get_new_packaging_request_ids_no_iam(api_url):
    """
    Test getNewPackagingRequestIds without IAM permission
    """

    query = """
    query {
        getNewPackagingRequestIds {
            nextToken
            packagingRequestIds
        }
    }
    """

    response = requests.post(api_url, json={"query": query})
    data = response.json()
    assert data.get("data", None) is None
    assert len(data["errors"]) > 0


def test_get_packaging_request(jwt_token, api_url, warehouse_table_name, get_order):
    """
    Test getPackagingRequest
    """

    # Create an order
    order = get_order()
    order_metadata = {
        "orderId": order["orderId"],
        "productId": "__metadata",
        "modifiedDate": order["modifiedDate"],
        "newDate": order["modifiedDate"],
        "status": "NEW"
    }

    # Seed the table
    table = boto3.resource("dynamodb").Table(warehouse_table_name) # pylint: disable=no-member
    with table.batch_writer() as batch:
        batch.put_item(Item=order_metadata)
        for product in order["products"]:
            batch.put_item(Item={
                "orderId": order["orderId"],
                "productId": product["productId"],
                "quantity": product.get("quantity", 1)
            })

    # Perform the query
    query = """
    query ($input: PackagingInput!) {
        getPackagingRequest(input: $input) {
            orderId
            status
            products {
                productId
                quantity
            }
        }
    }
    """

    response = requests.post(api_url,
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {"input": {"orderId": order["orderId"]}}
        }
    )
    data = response.json()
    print(data)

    assert "data" in data
    assert data["data"] is not None
    assert "getPackagingRequest" in data["data"]
    pr = data["data"]["getPackagingRequest"]
    assert pr["orderId"] == order["orderId"]
    assert pr["status"] == order["status"]
    assert len(pr["products"]) == len(order["products"])

    products = {p["productId"]: p for p in pr["products"]}
    for product in order["products"]:
        assert product["productId"] in products.keys()
        assert products[product["productId"]]["quantity"] == product.get("quantity", 1)

    # Cleanup
    with table.batch_writer() as batch:
        batch.delete_item(Key={
            "orderId": order["orderId"],
            "productId": "__metadata"
        })
        for product in order["products"]:
            batch.delete_item(Key={
                "orderId": order["orderId"],
                "productId": product["productId"]
            })


def test_start_packaging(jwt_token, api_url, warehouse_table_name):
    """
    Test startPackaging
    """

    order_metadata = {
        "orderId": str(uuid.uuid4()),
        "productId": "__metadata",
        "modifiedDate": datetime.datetime.now().isoformat(),
        "newDate": datetime.datetime.now().isoformat(),
        "status": "NEW"
    }

    print(order_metadata)

    # Seed the database
    table = boto3.resource("dynamodb").Table(warehouse_table_name) # pylint: disable=no-member
    table.put_item(Item=order_metadata)

    # Make request
    query = """
    mutation ($input: PackagingInput!) {
        startPackaging(input: $input) {
            success
        }
    }
    """

    response = requests.post(
        api_url, 
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {
                "input": {"orderId": order_metadata["orderId"]}
            }
        })
    data = response.json()
    print(data)
    assert "data" in data
    assert data["data"] is not None
    assert "startPackaging" in data["data"]
    assert "success" in data["data"]["startPackaging"]
    assert data["data"]["startPackaging"]["success"] == True

    ddb_res = table.get_item(Key={
        "orderId": order_metadata["orderId"],
        "productId": order_metadata["productId"]
    })
    assert "Item" in ddb_res
    assert "status" in ddb_res["Item"]
    assert "newDate" not in ddb_res["Item"]
    assert ddb_res["Item"]["status"] == "IN_PROGRESS"

    # Cleanup
    table.delete_item(Key={
        "orderId": order_metadata["orderId"],
        "productId": order_metadata["productId"]
    })


def test_complete_packaging(jwt_token, api_url, warehouse_table_name):
    """
    Test completePackaging
    """

    order_metadata = {
        "orderId": str(uuid.uuid4()),
        "productId": "__metadata",
        "modifiedDate": datetime.datetime.now().isoformat(),
        "status": "IN_PROGRESS"
    }

    print(order_metadata)

    # Seed the database
    table = boto3.resource("dynamodb").Table(warehouse_table_name) # pylint: disable=no-member
    table.put_item(Item=order_metadata)

    # Make request
    query = """
    mutation ($input: PackagingInput!) {
        completePackaging(input: $input) {
            success
        }
    }
    """

    response = requests.post(
        api_url, 
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {
                "input": {"orderId": order_metadata["orderId"]}
            }
        })
    data = response.json()
    print(data)
    assert "data" in data
    assert data["data"] is not None
    assert "completePackaging" in data["data"]
    assert "success" in data["data"]["completePackaging"]
    assert data["data"]["completePackaging"]["success"] == True

    ddb_res = table.get_item(Key={
        "orderId": order_metadata["orderId"],
        "productId": order_metadata["productId"]
    })
    assert "Item" in ddb_res
    assert "status" in ddb_res["Item"]
    assert ddb_res["Item"]["status"] == "COMPLETED"

    # Cleanup
    table.delete_item(Key={
        "orderId": order_metadata["orderId"],
        "productId": order_metadata["productId"]
    })


def test_get_new_deliveries(jwt_token, api_url, delivery_table_name, get_order):
    """
    Test getNewDeliveries
    """

    order = get_order()
    delivery = {
        "orderId": order["orderId"],
        "address": order["address"],
        "isNew": "true",
        "status": "NEW"
    }
    print(delivery)

    # Seed the database
    table = boto3.resource("dynamodb").Table(delivery_table_name) # pylint: disable=no-member
    table.put_item(Item=delivery)

    # Make requests
    headers = {"Authorization": jwt_token}
    def get_deliveries(next_token=None):
        if next_token:
            req_data = {
                "query": """
                query ($nextToken: String) {
                    getNewDeliveries(nextToken: $nextToken) {
                        nextToken
                        deliveries {
                            orderId
                            address {
                                name
                                streetAddress
                                city
                                country
                            }
                        }
                    }
                }
                """,
                "variables": {
                    "nextToken": next_token
                }
            }
        else:
            req_data = {
                "query": """
                query {
                    getNewDeliveries {
                        nextToken
                        deliveries {
                            orderId
                            address {
                                name
                                streetAddress
                                city
                                country
                            }
                        }
                    }
                }
                """
            }

        response = requests.post(api_url, json=req_data, headers=headers)
        data = response.json()

        print(data)

        assert "data" in data
        assert data["data"] is not None
        assert "getNewDeliveries" in data["data"]
        return data["data"]["getNewDeliveries"]

    found = False
    deliveries = get_deliveries()
    for delivery in deliveries["deliveries"]:
        if delivery["orderId"] == order["orderId"]:
            found = True
    while found == False and deliveries.get("nextToken", None) is not None:
        deliveries = get_deliveries(deliveries["nextToken"])
        for delivery in deliveries["deliveries"]:
            if delivery["orderId"] == order["orderId"]:
                found = True

    assert found == True

    # Clean database
    table.delete_item(Key={
        "orderId": order["orderId"]
    })


def test_get_delivery(jwt_token, api_url, delivery_table_name, get_order):
    """
    Test getDelivery
    """

    order = get_order()
    delivery = {
        "orderId": order["orderId"],
        "address": order["address"],
        "isNew": "true",
        "status": "NEW"
    }
    print(delivery)

    # Seed the database
    table = boto3.resource("dynamodb").Table(delivery_table_name) # pylint: disable=no-member
    table.put_item(Item=delivery)

    # Make request
    query = """
    query($input: DeliveryInput!) {
        getDelivery(input: $input) {
            orderId
            address {
                name
                streetAddress
                city
                country
            }
        }
    }
    """

    res = requests.post(
        api_url,
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {
                "input": {
                    "orderId": order["orderId"]
                }
            }
        }
    )

    data = res.json()
    print(data)
    assert "data" in data
    assert data["data"] is not None
    assert "getDelivery" in data["data"]
    assert "orderId" in data["data"]["getDelivery"]
    assert data["data"]["getDelivery"]["orderId"] == order["orderId"]
    assert data["data"]["getDelivery"]["address"]["name"] == order["address"]["name"]
    assert data["data"]["getDelivery"]["address"]["streetAddress"] == order["address"]["streetAddress"]
    assert data["data"]["getDelivery"]["address"]["city"] == order["address"]["city"]
    assert data["data"]["getDelivery"]["address"]["country"] == order["address"]["country"]

    # Cleanup
    table.delete_item(Key={
        "orderId": order["orderId"]
    })


def test_start_delivery(jwt_token, api_url, delivery_table_name, get_order):
    """
    Test startDelivery
    """

    order = get_order()
    delivery = {
        "orderId": order["orderId"],
        "address": order["address"],
        "isNew": "true",
        "status": "NEW"
    }
    print(delivery)

    # Seed the database
    table = boto3.resource("dynamodb").Table(delivery_table_name) # pylint: disable=no-member
    table.put_item(Item=delivery)

    # Make request
    query = """
    mutation ($input: DeliveryInput!) {
        startDelivery(input: $input) {
            success
        }
    }
    """

    res = requests.post(
        api_url,
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {
                "input": {
                    "orderId": order["orderId"]
                }
            }
        }
    )
    data = res.json()
    print(data)

    assert "data" in data
    assert data["data"] is not None
    assert "startDelivery" in data["data"]
    assert "success" in data["data"]["startDelivery"]
    assert data["data"]["startDelivery"]["success"] == True

    ddb_res = table.get_item(Key={
        "orderId": order["orderId"]
    })
    assert "Item" in ddb_res
    assert "status" in ddb_res["Item"]
    assert ddb_res["Item"]["status"] == "IN_PROGRESS"
    assert "isNew" not in ddb_res["Item"]

    # Cleanup
    table.delete_item(Key={
        "orderId": order["orderId"]
    })


def test_fail_delivery(jwt_token, api_url, delivery_table_name, get_order):
    """
    Test failDelivery
    """

    order = get_order()
    delivery = {
        "orderId": order["orderId"],
        "address": order["address"],
        "status": "IN_PROGRESS"
    }
    print(delivery)

    # Seed the database
    table = boto3.resource("dynamodb").Table(delivery_table_name) # pylint: disable=no-member
    table.put_item(Item=delivery)

    # Make request
    query = """
    mutation ($input: DeliveryInput!) {
        failDelivery(input: $input) {
            success
        }
    }
    """

    res = requests.post(
        api_url,
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {
                "input": {
                    "orderId": order["orderId"]
                }
            }
        }
    )
    data = res.json()
    print(data)

    assert "data" in data
    assert data["data"] is not None
    assert "failDelivery" in data["data"]
    assert "success" in data["data"]["failDelivery"]
    assert data["data"]["failDelivery"]["success"] == True

    ddb_res = table.get_item(Key={
        "orderId": order["orderId"]
    })
    assert "Item" in ddb_res
    assert "status" in ddb_res["Item"]
    assert ddb_res["Item"]["status"] == "FAILED"

    # Cleanup
    table.delete_item(Key={
        "orderId": order["orderId"]
    })


def test_complete_delivery(jwt_token, api_url, delivery_table_name, get_order):
    """
    Test failDelivery
    """

    order = get_order()
    delivery = {
        "orderId": order["orderId"],
        "address": order["address"],
        "status": "IN_PROGRESS"
    }
    print(delivery)

    # Seed the database
    table = boto3.resource("dynamodb").Table(delivery_table_name) # pylint: disable=no-member
    table.put_item(Item=delivery)

    # Make request
    query = """
    mutation ($input: DeliveryInput!) {
        completeDelivery(input: $input) {
            success
        }
    }
    """

    res = requests.post(
        api_url,
        headers={"Authorization": jwt_token},
        json={
            "query": query,
            "variables": {
                "input": {
                    "orderId": order["orderId"]
                }
            }
        }
    )
    data = res.json()
    print(data)

    assert "data" in data
    assert data["data"] is not None
    assert "completeDelivery" in data["data"]
    assert "success" in data["data"]["completeDelivery"]
    assert data["data"]["completeDelivery"]["success"] == True

    ddb_res = table.get_item(Key={
        "orderId": order["orderId"]
    })
    assert "Item" in ddb_res
    assert "status" in ddb_res["Item"]
    assert ddb_res["Item"]["status"] == "COMPLETED"

    # Cleanup
    table.delete_item(Key={
        "orderId": order["orderId"]
    })